/*
 * Copyright 2012 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.workbench.models.datamodel.rule;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.drools.workbench.models.datamodel.imports.HasImports;
import org.drools.workbench.models.datamodel.imports.Imports;
import org.drools.workbench.models.datamodel.packages.HasPackageName;

public class RuleModel implements HasImports,
                                  HasPackageName {

    /**
     * This name is generally not used - the asset name or the file name is
     * preferred (ie it could get out of sync with the name of the file it is
     * in).
     */
    public String name;
    public String parentName;
    public String modelVersion = "1.0";
    public RuleAttribute[] attributes = new RuleAttribute[ 0 ];
    public RuleMetadata[] metadataList = new RuleMetadata[ 0 ];
    public IPattern[] lhs = new IPattern[ 0 ];
    public IAction[] rhs = new IAction[ 0 ];

    private Imports imports = new Imports();

    private String packageName;

    //Is the Rule to be negated (i.e. "not ( PatternX, PatternY... )"
    private boolean isNegated;

    public RuleModel() {
    }

    /**
     * This will return a List<String> of all FactPattern bindings
     * @return The bindings or an empty list if no bindings are found.
     */
    public List<String> getLHSBoundFacts() {
        if ( this.lhs == null ) {
            return Collections.emptyList();
        }
        final List<String> list = new ArrayList<String>();
        for ( int i = 0; i < this.lhs.length; i++ ) {
            IPattern pat = this.lhs[ i ];
            if ( pat instanceof FromCompositeFactPattern ) {
                pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
            }
            if ( pat instanceof FactPattern ) {
                final FactPattern p = (FactPattern) pat;
                if ( p.getBoundName() != null ) {
                    list.add( p.getBoundName() );
                }
            }
        }
        return list;
    }

    /**
     * This will return the FactPattern that a variable is bound Eto.
     * @param var The bound fact variable (NOT bound field).
     * @return null or the FactPattern found.
     */
    public FactPattern getLHSBoundFact( final String var ) {
        if ( this.lhs == null ) {
            return null;
        }
        for ( int i = 0; i < this.lhs.length; i++ ) {
            IPattern pat = this.lhs[ i ];
            if ( pat instanceof FromCompositeFactPattern ) {
                pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
            }
            if ( pat instanceof FactPattern ) {
                final FactPattern p = (FactPattern) pat;
                if ( p.getBoundName() != null && var.equals( p.getBoundName() ) ) {
                    return p;
                }
            }
        }
        return null;
    }

    /**
     * This will return the FieldConstraint that a variable is bound to.
     * @param var The bound field variable (NOT bound fact).
     * @return null or the FieldConstraint found.
     */
    public SingleFieldConstraint getLHSBoundField( final String var ) {
        if ( this.lhs == null ) {
            return null;
        }
        for ( int i = 0; i < this.lhs.length; i++ ) {
            IPattern pat = this.lhs[ i ];

            SingleFieldConstraint fieldConstraint = getLHSBoundField( pat, var );
            if ( fieldConstraint != null ) {
                return fieldConstraint;
            }
        }

        return null;
    }

    private SingleFieldConstraint getLHSBoundField( IPattern pat,
                                                    String var ) {
        if ( pat instanceof FromCompositeFactPattern ) {
            pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
        }

        if ( pat instanceof CompositeFactPattern ) {
            for ( IPattern iPattern : ( (CompositeFactPattern) pat ).getPatterns() ) {
                SingleFieldConstraint fieldConstraint = getLHSBoundField( iPattern, var );
                if ( fieldConstraint != null ) {
                    return fieldConstraint;
                }
            }
        }

        if ( pat instanceof FactPattern ) {
            final FactPattern p = (FactPattern) pat;
            for ( int j = 0; j < p.getFieldConstraints().length; j++ ) {
                if ( p.getFieldConstraints()[ j ] instanceof SingleFieldConstraint ) {
                    SingleFieldConstraint fc = (SingleFieldConstraint) p.getFieldConstraints()[ j ];

                    List<String> fieldBindings = getFieldBinding( fc );
                    if ( fieldBindings.contains( var ) ) {
                        return fc;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Get the data-type associated with the binding
     * @param var
     * @return The data-type, or null if the binding could not be found
     */
    public String getLHSBindingType( final String var ) {
        if ( this.lhs == null ) {
            return null;
        }
        for ( int i = 0; i < this.lhs.length; i++ ) {
            String type = getLHSBindingType( this.lhs[ i ], var );
            if ( type != null ) {
                return type;
            }
        }
        return null;
    }

    private String getLHSBindingType( IPattern pat,
                                      String var ) {
        if ( pat instanceof FromCompositeFactPattern ) {
            pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
        }

        if ( pat instanceof CompositeFactPattern ) {
            for ( IPattern iPattern : ( (CompositeFactPattern) pat ).getPatterns() ) {
                String type = getLHSBindingType( iPattern, var );
                if ( type != null ) {
                    return type;
                }
            }
        }
        if ( pat instanceof FactPattern ) {
            final FactPattern p = (FactPattern) pat;
            if ( p.isBound() && var.equals( p.getBoundName() ) ) {
                return p.getFactType();
            }
            for ( FieldConstraint fc : p.getFieldConstraints() ) {
                String type = getFieldBinding( fc,
                                               var );
                if ( type != null ) {
                    return type;
                }
            }
        }
        return null;
    }

    public String getFieldBinding( FieldConstraint fc,
                                   String var ) {
        String fieldType = null;
        if ( fc instanceof SingleFieldConstraint ) {
            SingleFieldConstraint s = (SingleFieldConstraint) fc;
            if ( s.isBound() && var.equals( s.getFieldBinding() ) ) {
                fieldType = s.getFieldType();
            }
        }
        if ( fc instanceof SingleFieldConstraintEBLeftSide ) {
            SingleFieldConstraintEBLeftSide s = (SingleFieldConstraintEBLeftSide) fc;
            if ( s.isBound() && var.equals( s.getFieldBinding() ) ) {
                fieldType = s.getExpressionLeftSide().getGenericType();
            }
        }
        if ( fc instanceof CompositeFieldConstraint ) {
            CompositeFieldConstraint s = (CompositeFieldConstraint) fc;
            if ( s.getConstraints() != null ) {
                for ( FieldConstraint ss : s.getConstraints() ) {
                    fieldType = getFieldBinding( ss,
                                                 var );
                }
            }
        }
        return fieldType;
    }

    /**
     * This will return a List<String> of all ActionInsertFact bindings
     * @return The bindings or an empty list if no bindings are found.
     */
    public List<String> getRHSBoundFacts() {
        if ( this.rhs == null ) {
            return null;
        }
        final List<String> list = new ArrayList<String>();
        for ( int i = 0; i < this.rhs.length; i++ ) {
            if ( this.rhs[ i ] instanceof ActionInsertFact ) {
                final ActionInsertFact p = (ActionInsertFact) this.rhs[ i ];
                if ( p.getBoundName() != null ) {
                    list.add( p.getBoundName() );
                }
            }
        }
        return list;
    }

    /**
     * This will return the ActionInsertFact that a variable is bound to.
     * @param var The bound fact variable (NOT bound field).
     * @return null or the ActionInsertFact found.
     */
    public ActionInsertFact getRHSBoundFact( final String var ) {
        if ( this.rhs == null ) {
            return null;
        }
        for ( int i = 0; i < this.rhs.length; i++ ) {
            if ( this.rhs[ i ] instanceof ActionInsertFact ) {
                final ActionInsertFact p = (ActionInsertFact) this.rhs[ i ];
                if ( p.getBoundName() != null && var.equals( p.getBoundName() ) ) {
                    return p;
                }
            }
        }
        return null;
    }

    /**
     * This will return the FactPattern that a variable is bound to. If the
     * variable is bound to a FieldConstraint the parent FactPattern will be
     * returned.
     * @param var The variable binding
     * @return null or the FactPattern found.
     */
    public FactPattern getLHSParentFactPatternForBinding( final String var ) {
        if ( this.lhs == null ) {
            return null;
        }
        for ( int i = 0; i < this.lhs.length; i++ ) {
            IPattern pat = this.lhs[ i ];
            if ( pat instanceof FromCompositeFactPattern ) {
                pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
            }
            if ( pat instanceof FactPattern ) {
                final FactPattern p = (FactPattern) pat;
                if ( p.getBoundName() != null && var.equals( p.getBoundName() ) ) {
                    return p;
                }
                for ( int j = 0; j < p.getFieldConstraints().length; j++ ) {
                    FieldConstraint fc = p.getFieldConstraints()[ j ];
                    List<String> fieldBindings = getFieldBinding( fc );
                    if ( fieldBindings.contains( var ) ) {
                        return p;
                    }
                }
            }
        }
        return null;
    }

    /**
     * This will get a list of all LHS bound variables, including bound fields..
     */
    public List<String> getAllLHSVariables() {
        List<String> result = new ArrayList<String>();

        for ( int i = 0; i < this.lhs.length; i++ ) {
            IPattern pat = this.lhs[ i ];
            if ( pat instanceof FromCompositeFactPattern ) {
                pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
            }
            if ( pat instanceof FactPattern ) {
                FactPattern fact = (FactPattern) pat;
                if ( fact.isBound() ) {
                    result.add( fact.getBoundName() );
                }

                for ( int j = 0; j < fact.getFieldConstraints().length; j++ ) {
                    FieldConstraint fc = fact.getFieldConstraints()[ j ];
                    if ( fc instanceof SingleFieldConstraintEBLeftSide ) {
                        SingleFieldConstraintEBLeftSide exp = (SingleFieldConstraintEBLeftSide) fc;
                        if ( exp.getExpressionLeftSide() != null && exp.getExpressionLeftSide().isBound() ) {
                            result.add( exp.getExpressionLeftSide().getBinding() );
                        }
                    } else if ( fc instanceof SingleFieldConstraint ) {
                        SingleFieldConstraint con = (SingleFieldConstraint) fc;
                        if ( con.isBound() ) {
                            result.add( con.getFieldBinding() );
                        }
                        if ( con.getExpressionValue() != null && con.getExpressionValue().isBound() ) {
                            result.add( con.getExpressionValue().getBinding() );
                        }
                    }
                }
            }
        }

        return result;
    }

    /**
     * This will get a list of all RHS bound variables.
     */
    public List<String> getAllRHSVariables() {
        List<String> result = new ArrayList<String>();

        for ( int i = 0; i < this.rhs.length; i++ ) {
            IAction pat = this.rhs[ i ];
            if ( pat instanceof ActionInsertFact ) {
                ActionInsertFact fact = (ActionInsertFact) pat;
                if ( fact.isBound() ) {
                    result.add( fact.getBoundName() );
                }
            }
        }

        return result;
    }

    /**
     * This will get a list of all bound variables (LHS and RHS), including bound fields..
     */
    public List<String> getAllVariables() {
        List<String> result = new ArrayList<String>();
        result.addAll( this.getAllLHSVariables() );
        result.addAll( this.getAllRHSVariables() );
        return result;
    }

    public List<String> getFieldBinding( FieldConstraint f ) {
        List<String> result = new ArrayList<String>();
        if ( f instanceof SingleFieldConstraint ) {
            SingleFieldConstraint con = (SingleFieldConstraint) f;
            if ( con.isBound() ) {
                result.add( con.getFieldBinding() );
            }
            if ( con.getExpressionValue() != null && con.getExpressionValue().isBound() ) {
                result.add( con.getExpressionValue().getBinding() );
            }
            if ( con instanceof SingleFieldConstraintEBLeftSide ) {
                SingleFieldConstraintEBLeftSide exp = (SingleFieldConstraintEBLeftSide) con;
                if ( exp.getExpressionLeftSide() != null && exp.getExpressionLeftSide().isBound() ) {
                    result.add( exp.getExpressionLeftSide().getBinding() );
                }
            }

        } else if ( f instanceof CompositeFieldConstraint ) {
            CompositeFieldConstraint cfc = (CompositeFieldConstraint) f;
            if ( cfc.getConstraints() != null ) {
                for ( FieldConstraint ss : cfc.getConstraints() ) {
                    List<String> t = getFieldBinding( ss );
                    result.addAll( t );
                }
            }
        }
        return result;
    }

    /**
     * @param idx Remove this index from the LHS. returns false if it was NOT
     * allowed to remove this item (ie it is used on the RHS).
     */
    public boolean removeLhsItem( final int idx ) {

        final IPattern[] newList = new IPattern[ this.lhs.length - 1 ];
        int newIdx = 0;
        for ( int i = 0; i < this.lhs.length; i++ ) {

            if ( i != idx ) {
                newList[ newIdx ] = this.lhs[ i ];
                newIdx++;
            } else {
                IPattern pat = this.lhs[ i ];
                if ( pat instanceof FromCompositeFactPattern ) {
                    pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
                }
                if ( pat instanceof FactPattern ) {
                    final FactPattern p = (FactPattern) pat;
                    if ( p.getBoundName() != null && isBoundFactUsed( p.getBoundName() ) ) {
                        return false;
                    }
                }

            }

        }
        this.lhs = newList;
        return true;
    }

    /**
     * @param binding The name of the LHS fact binding.
     * @return Returns true if the specified binding is used on the RHS.
     */
    public boolean isBoundFactUsed( final String binding ) {
        if ( this.rhs == null ) {
            return false;
        }
        for ( int i = 0; i < this.rhs.length; i++ ) {
            if ( this.rhs[ i ] instanceof ActionSetField ) {
                final ActionSetField set = (ActionSetField) this.rhs[ i ];
                if ( set.getVariable().equals( binding ) ) {
                    return true;
                }
            } else if ( this.rhs[ i ] instanceof ActionRetractFact ) {
                final ActionRetractFact ret = (ActionRetractFact) this.rhs[ i ];
                if ( ret.getVariableName().equals( binding ) ) {
                    return true;
                }
            }
        }
        return false;
    }

    public void addLhsItem( final IPattern pat ) {
        this.addLhsItem( pat,
                         true );
    }

    public void addLhsItem( final IPattern pat,
                            boolean append ) {
        this.addLhsItem( pat,
                         append ? this.lhs.length : 0 );
    }

    public void addLhsItem( final IPattern pat,
                            int position ) {
        if ( this.lhs == null ) {
            this.lhs = new IPattern[ 0 ];
        }

        if ( position < 0 ) {
            position = 0;
        } else if ( position > this.lhs.length ) {
            position = this.lhs.length;
        }

        final IPattern[] list = this.lhs;
        final IPattern[] newList = new IPattern[ list.length + 1 ];

        for ( int i = 0; i < newList.length; i++ ) {
            if ( i < position ) {
                newList[ i ] = list[ i ];
            } else if ( i > position ) {
                newList[ i ] = list[ i - 1 ];
            } else {
                newList[ i ] = pat;
            }

        }

        this.lhs = newList;
    }

    public void moveLhsItemDown( int itemIndex ) {
        if ( this.lhs == null ) {
            this.lhs = new IPattern[ 0 ];
        }

        this.moveItemDown( this.lhs,
                           itemIndex );
    }

    public void moveLhsItemUp( int itemIndex ) {
        if ( this.lhs == null ) {
            this.lhs = new IPattern[ 0 ];
        }

        this.moveItemUp( this.lhs,
                         itemIndex );
    }

    public void moveRhsItemDown( int itemIndex ) {
        if ( this.rhs == null ) {
            this.rhs = new IAction[ 0 ];
        }

        this.moveItemDown( this.rhs,
                           itemIndex );
    }

    public void moveRhsItemUp( int itemIndex ) {
        if ( this.rhs == null ) {
            this.rhs = new IAction[ 0 ];
        }

        this.moveItemUp( this.rhs,
                         itemIndex );
    }

    private void moveItemDown( Object[] array,
                               int itemIndex ) {

        for ( int i = 0; i < array.length; i++ ) {
            if ( i == itemIndex ) {
                if ( array.length > ( i + 1 ) ) {
                    Object tmp = array[ i + 1 ];
                    array[ i + 1 ] = array[ i ];
                    array[ i ] = tmp;
                    i++;
                }
            }
        }
    }

    private void moveItemUp( Object[] array,
                             int itemIndex ) {
        if ( itemIndex == 0 ) {
            return;
        }
        for ( int i = 0; i < array.length; i++ ) {
            if ( i == itemIndex ) {
                Object tmp = array[ i - 1 ];
                array[ i - 1 ] = array[ i ];
                array[ i ] = tmp;
            }
        }
    }

    public void addRhsItem( final IAction action ) {
        this.addRhsItem( action,
                         true );
    }

    public void addRhsItem( final IAction action,
                            boolean append ) {
        this.addRhsItem( action,
                         append ? this.rhs.length : 0 );
    }

    public void addRhsItem( final IAction action,
                            int position ) {
        if ( this.rhs == null ) {
            this.rhs = new IAction[ 0 ];
        }

        if ( position < 0 ) {
            position = 0;
        } else if ( position > this.rhs.length ) {
            position = this.rhs.length;
        }

        final IAction[] list = this.rhs;
        final IAction[] newList = new IAction[ list.length + 1 ];

        for ( int i = 0; i < newList.length; i++ ) {
            if ( i < position ) {
                newList[ i ] = list[ i ];
            } else if ( i > position ) {
                newList[ i ] = list[ i - 1 ];
            } else {
                newList[ i ] = action;
            }
        }

        this.rhs = newList;
    }

    public void removeRhsItem( final int idx ) {
        final IAction[] newList = new IAction[ this.rhs.length - 1 ];
        int newIdx = 0;
        for ( int i = 0; i < this.rhs.length; i++ ) {

            if ( i != idx ) {
                newList[ newIdx ] = this.rhs[ i ];
                newIdx++;
            }

        }
        this.rhs = newList;
    }

    public void addAttribute( final RuleAttribute attribute ) {

        final RuleAttribute[] list = this.attributes;
        final RuleAttribute[] newList = new RuleAttribute[ list.length + 1 ];

        for ( int i = 0; i < list.length; i++ ) {
            newList[ i ] = list[ i ];
        }
        newList[ list.length ] = attribute;

        this.attributes = newList;

    }

    public void removeAttribute( final int idx ) {
        final RuleAttribute[] newList = new RuleAttribute[ this.attributes.length - 1 ];
        int newIdx = 0;
        for ( int i = 0; i < this.attributes.length; i++ ) {
            if ( i != idx ) {
                newList[ newIdx ] = this.attributes[ i ];
                newIdx++;
            }

        }
        this.attributes = newList;
    }

    /**
     * Add metaData
     * @param metadata
     */
    public void addMetadata( final RuleMetadata metadata ) {

        final RuleMetadata[] newList = new RuleMetadata[ this.metadataList.length + 1 ];

        for ( int i = 0; i < this.metadataList.length; i++ ) {
            newList[ i ] = this.metadataList[ i ];
        }
        newList[ this.metadataList.length ] = metadata;

        this.metadataList = newList;
    }

    public void removeMetadata( final int idx ) {
        final RuleMetadata[] newList = new RuleMetadata[ this.metadataList.length - 1 ];
        int newIdx = 0;
        for ( int i = 0; i < this.metadataList.length; i++ ) {

            if ( i != idx ) {
                newList[ newIdx ] = this.metadataList[ i ];
                newIdx++;
            }

        }
        this.metadataList = newList;

    }

    /**
     * Locate metadata element
     * @param attributeName - value to look for
     * @return null if not found
     */
    public RuleMetadata getMetaData( String attributeName ) {

        if ( metadataList != null && attributeName != null ) {
            for ( int i = 0; i < metadataList.length; i++ ) {
                if ( attributeName.equals( metadataList[ i ].getAttributeName() ) ) {
                    return metadataList[ i ];
                }
            }
        }
        return null;
    }

    /**
     * Update metaData element if it exists or add it otherwise
     * @param target
     * @return true on update of existing element false on added of element
     */
    public boolean updateMetadata( final RuleMetadata target ) {

        RuleMetadata metaData = getMetaData( target.getAttributeName() );
        if ( metaData != null ) {
            metaData.setValue( target.getValue() );
            return true;
        }

        addMetadata( target );
        return false;
    }

    /**
     * This uses a deceptively simple algorithm to determine what bound
     * variables are in scope for a given constraint (including connectives).
     * Does not take into account globals.
     */
    public List<String> getBoundVariablesInScope( final BaseSingleFieldConstraint con ) {
        final List<String> result = new ArrayList<String>();
        for ( int i = 0; i < this.lhs.length; i++ ) {
            IPattern pat = this.lhs[ i ];
            if ( findBoundVariableNames( con, result, pat ) ) {
                return result;
            }
        }
        return result;
    }

    private boolean findBoundVariableNames( BaseSingleFieldConstraint con,
                                            List<String> result,
                                            IPattern pat ) {
        if ( pat instanceof FromCompositeFactPattern ) {
            pat = ( (FromCompositeFactPattern) pat ).getFactPattern();
        }

        if ( pat instanceof CompositeFactPattern ) {
            for ( IFactPattern p : ( (CompositeFactPattern) pat ).getPatterns() ) {
                findBoundVariableNames( con, result, p );
            }
        }

        if ( pat instanceof FactPattern ) {
            final FactPattern fact = (FactPattern) pat;

            if ( findBoundVariableNames( con, result, fact ) ) {
                return true;
            }

        }
        return false;
    }

    private boolean findBoundVariableNames( BaseSingleFieldConstraint con,
                                            List<String> result,
                                            FactPattern fact ) {
        if ( fact.getConstraintList() != null ) {
            final FieldConstraint[] cons = fact.getConstraintList().getConstraints();
            if ( cons != null ) {
                for ( int k = 0; k < cons.length; k++ ) {
                    FieldConstraint fc = cons[ k ];
                    if ( fc instanceof SingleFieldConstraint ) {
                        final SingleFieldConstraint c = (SingleFieldConstraint) fc;
                        if ( c == con ) {
                            return true;
                        }
                        if ( c.getConnectives() != null ) {
                            for ( int j = 0; j < c.getConnectives().length; j++ ) {
                                if ( con == c.getConnectives()[ j ] ) {
                                    return true;
                                }
                            }
                        }
                        if ( c.isBound() ) {
                            result.add( c.getFieldBinding() );
                        }
                    }
                }
            }
            if ( fact.isBound() ) {
                result.add( fact.getBoundName() );
            }
        } else {
            if ( fact.isBound() ) {
                result.add( fact.getBoundName() );
            }
        }
        return false;
    }

    /**
     * Checks to see if a variable is used or not, includes fields as well as
     * facts.
     */
    public boolean isVariableNameUsed( String s ) {
        return getAllVariables().contains( s );
    }

    /**
     * Returns true if any DSLSentences are used.
     */
    public boolean hasDSLSentences() {

        if ( this.lhs != null ) {
            for ( IPattern pattern : this.lhs ) {
                if ( pattern instanceof DSLSentence ) {
                    return true;
                }
            }
        }

        if ( this.rhs != null ) {
            for ( IAction action : this.rhs ) {
                if ( action instanceof DSLSentence ) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Is the Rule to be negated, i.e. "not ( PatternX, PatternY... )"
     * @return
     */
    public boolean isNegated() {
        return isNegated;
    }

    /**
     * Set whether the Rule is to be negated
     * @param isNegated
     */
    public void setNegated( boolean isNegated ) {
        this.isNegated = isNegated;
    }

    public Imports getImports() {
        return imports;
    }

    @Override
    public void setImports( final Imports imports ) {
        this.imports = imports;
    }

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName( String packageName ) {
        this.packageName = packageName;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        RuleModel ruleModel = (RuleModel) o;

        if (isNegated != ruleModel.isNegated) return false;
        if (!Arrays.equals(attributes, ruleModel.attributes)) return false;
        if (imports != null ? !imports.equals(ruleModel.imports) : ruleModel.imports != null) return false;
        if (!Arrays.equals(lhs, ruleModel.lhs)) return false;
        if (!Arrays.equals(metadataList, ruleModel.metadataList)) return false;
        if (modelVersion != null ? !modelVersion.equals(ruleModel.modelVersion) : ruleModel.modelVersion != null)
            return false;
        if (name != null ? !name.equals(ruleModel.name) : ruleModel.name != null) return false;
        if (packageName != null ? !packageName.equals(ruleModel.packageName) : ruleModel.packageName != null)
            return false;
        if (parentName != null ? !parentName.equals(ruleModel.parentName) : ruleModel.parentName != null) return false;
        if (!Arrays.equals(rhs, ruleModel.rhs)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = ~~result;
        result = 31 * result + (parentName != null ? parentName.hashCode() : 0);
        result = ~~result;
        result = 31 * result + (modelVersion != null ? modelVersion.hashCode() : 0);
        result = ~~result;
        result = 31 * result + (attributes != null ? Arrays.hashCode(attributes) : 0);
        result = ~~result;
        result = 31 * result + (metadataList != null ? Arrays.hashCode(metadataList) : 0);
        result = ~~result;
        result = 31 * result + (lhs != null ? Arrays.hashCode(lhs) : 0);
        result = ~~result;
        result = 31 * result + (rhs != null ? Arrays.hashCode(rhs) : 0);
        result = ~~result;
        result = 31 * result + (imports != null ? imports.hashCode() : 0);
        result = ~~result;
        result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
        result = ~~result;
        result = 31 * result + (isNegated ? 1 : 0);
        result = ~~result;
        return result;
    }
}
